﻿using System;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;

namespace SharePoint.Contrib
{
    /// <summary>
    /// Utility class to convert between .NET types
    /// </summary>
    /// <typeparam name="T">the type to convert to</typeparam> 
    public static class GenericDataConverter<T>
    {
        /// <summary>
        /// Converts the object to typeof(T)
        /// </summary>
        /// <exception cref="System.InvalidCastException" />
        public static readonly Converter<object, T> Convert;

        static GenericDataConverter()
        {
            GenericDataConverter<T>.Convert = GenericDataConverter<T>.Create(typeof(T));
        }

        private static Converter<object, T> Create(Type type)
        {
            if (type.IsClass == true)
            {
                // reference type converter delegate
                return new Converter<object, T>(GenericDataConverter<T>.ReferenceType);
            }

            // nullable type converter delegate
            if ((type.IsGenericType && type.IsGenericTypeDefinition == false)
                && (typeof(Nullable<>) == type.GetGenericTypeDefinition()))
            {
                return (Converter<object, T>)Delegate.CreateDelegate(
                    typeof(Converter<object, T>),
                    typeof(GenericDataConverter<T>).GetMethod(
                        "NullableField", BindingFlags.NonPublic
                        | BindingFlags.Static).MakeGenericMethod(
                            new Type[] { type.GetGenericArguments()[0] }));
            }

            // value type converter delegate
            return new Converter<object, T>(GenericDataConverter<T>.ValueType);
        }

        #region Converter Delegates
        private static T ReferenceType(object value)
        {
            if (value == null)
            {
                return default(T);
            }
            else if (typeof(T).IsAssignableFrom(value.GetType()) == true)
            {
                return (T)value;
            }

            return default(T);
        }

        private static T ValueType(object value)
        {
            var convertible = value as IConvertible;
            if (convertible != null)
            {
                return (T)System.Convert.ChangeType(convertible, typeof(T));
            }

            var converter = TypeDescriptor.GetConverter(value);

            if (converter.CanConvertTo(typeof(T)))
            {
                return (T)converter.ConvertTo(value, typeof(T));
            }

            if (value == null)
            {
                throw new InvalidCastException(string.Format(CultureInfo.InvariantCulture,
                    "Unable to cast type '{0}'.", typeof(T).Name));
            }

            return default(T);

        }

        private static U? NullableField<U>(object value) where U : struct
        {
            if (value == null)
            {
                return (U?)null;
            }

            var convertible = value as IConvertible;
            if (convertible != null)
            {
                return (U)System.Convert.ChangeType(convertible, typeof(U));
            }

            var converter = TypeDescriptor.GetConverter(value);

            if (converter.CanConvertTo(typeof(T)))
            {
                return (U)converter.ConvertTo(value, typeof(T));
            }

            return new U?((U)value);
        }


        #endregion


    }

}
